/** @file md5_mesh.cpp
    @brief MD5 model meshes */

#include "md5/md5_object.h"

using namespace G3;

//==============================================================================
// Constructors
//==============================================================================
G3::MD5_MODEL::JOINT::JOINT () {
    parent = 0;
}

G3::MD5_MODEL::VERTEX::VERTEX () {
    weightStart = 0;
    numWeights = 0;
}

G3::MD5_MODEL::TRIANGLE::TRIANGLE () {
    indices [0] = 0;
    indices [1] = 0;
    indices [2] = 0;
}

G3::MD5_MODEL::WEIGHT::WEIGHT () {
    joint = 0;
    bias = 0.0f;
}

G3::MD5_MODEL::SKELETON::SKELETON () {
    log = &G3::MainLog;
}

G3::MD5_MODEL::MESH::MESH () {
    visible = true;

    numVertices = 0;
    numTriangles = 0;
    numWeights = 0;

    vertices = NULL;
    normals = NULL;
    tangents = NULL;
    texcoords = NULL;
    indices = NULL;

    log = &G3::MainLog;
}

//==============================================================================
// Destructors
//==============================================================================
G3::MD5_MODEL::JOINT::~JOINT () {
    name = "";
}

G3::MD5_MODEL::SKELETON::~SKELETON () {
    if (!joints.empty ())
        joints.clear ();
}

G3::MD5_MODEL::MESH::~MESH () {
    name = "";
    materialName = "";

    if (!preVerts.empty ())
        preVerts.clear ();
    if (!triangles.empty ())
        triangles.clear ();
    if (!weights.empty ())
        weights.clear ();

    if (vertices) {
        delete [] vertices;
        vertices = NULL;
    }
    if (normals) {
        delete [] normals;
        normals = NULL;
    }
    if (tangents) {
        delete [] tangents;
        tangents = NULL;
    }
    if (texcoords) {
        delete [] texcoords;
        texcoords = NULL;
    }
    if (indices) {
        delete [] indices;
        indices = NULL;
    }
}

//==============================================================================
// Operators
//==============================================================================
MD5_MODEL::BASEFRAME_JOINT& G3::MD5_MODEL::BASEFRAME_JOINT::operator= (const BASEFRAME_JOINT &bfj) {
    position = bfj.position;
    orientation = bfj.orientation;

    return *this;
}

//==============================================================================
// Clones the skeleton
//==============================================================================
MD5_MODEL::SKELETON *G3::MD5_MODEL::SKELETON::Clone () const {
    SKELETON *skel = NULL;

    try {
        skel = new SKELETON ();

        if (!skel)
            throw 1;
    } catch (...) {
        if (log)
            log->Report ("MD5_MODEL::SKELETON::Clone (): Failed to create a new skeleton..\n");
        return NULL;
    }

    skel->log = log;
    skel->joints = joints;

    return skel;
}

//==============================================================================
// Parses the skeleton
//==============================================================================
bool G3::MD5_MODEL::SKELETON::Parse (G3::STREAM *stream, int numJoints) {
    Assert (stream, "MD5_MODEL::SKELETON::Parse: Stream argument is a NULL pointer..");

    std::string line;
    G3::TOKENLIST tokl;
    JOINT *joint = NULL;

    joints.reserve (numJoints);

    // Parse the joints scope
    while (!stream->EndOfFile ()) {
        // Read an uncommented line
        line = G3::CopyUntilDelim (stream->Readline ("\r\n"), "//");
        // Tokenize the line
        tokl = G3::Tokenize (line, " \t\"(){}");

        // There are at least 8 important tokens?
        if (tokl.Tokens.size () >= 8) {
            // Allocate memory for a new joint
            joint = new JOINT ();
            // FAIL?
            if (!joint) {
                if (log)
                    log->Report ("ERROR! MD5_MODEL::SKELETON::Parse: Failed to allocate memory for a joint..\n");
                return false;
            }
            // Fetch joint name
            joint->name = tokl.Tokens [0];
            // Fetch joint parent id
            joint->parent = (int) G3::StrToInteger (tokl.Tokens [1]);
            // Fetch joint position
            joint->position.x = G3::StrToFloat (tokl.Tokens [2]);
            joint->position.y = G3::StrToFloat (tokl.Tokens [3]);
            joint->position.z = G3::StrToFloat (tokl.Tokens [4]);
            // Fetch joint orientation
            joint->orientation.x = G3::StrToFloat (tokl.Tokens [5]);
            joint->orientation.y = G3::StrToFloat (tokl.Tokens [6]);
            joint->orientation.z = G3::StrToFloat (tokl.Tokens [7]);
            joint->orientation.CalcW ();
            // Add this joint
            joints.push_back (joint);
        }

        // End of scope?
        if (line.find ("}") != std::string::npos)
            break;
    }

    return true;
}

//==============================================================================
// Parses the mesh
//==============================================================================
bool G3::MD5_MODEL::MESH::Parse (G3::STREAM *stream) {
    Assert (stream, "MD5_MODEL::MESH::Parse: Stream argument is a NULL pointer..");

    std::string line;
    G3::TOKENLIST tokl;
    VERTEX *vert = NULL;
    TRIANGLE *tri = NULL;
    WEIGHT *weight = NULL;

    // Parse the mesh scope
    while (!stream->EndOfFile ()) {
        // Read an uncommented line
        line = G3::CopyUntilDelim (stream->Readline ("\r\n"), "//");
        // Tokenize the line
        tokl = G3::Tokenize (line, " \t\"(){}");

        // There are at least 2 important tokens?
        if (tokl.Tokens.size () >= 2) {
            // TODO:: Make faster by remembering the number of read vertices / tris / weights - no need for those 'if's
            // A weight?
            if (tokl.Tokens [0] == "weight") {
                // Allocate memory for a new weight
                weight = new WEIGHT ();
                // FAIL?
                if (!weight) {
                    if (log)
                        log->Report ("ERROR! MD5_MODEL::MESH::Parse: Failed to allocate memory for a weight..\n");
                    return false;
                }
                // NOTE:: We skip the index in tokl.Tokens [1]
                // Fetch the weight joint number
                weight->joint = (int) G3::StrToInteger (tokl.Tokens [2]);
                // Fetch the weight bias
                weight->bias = G3::StrToFloat (tokl.Tokens [3]);
                // Fetch the weight position
                weight->position.x = G3::StrToFloat (tokl.Tokens [4]);
                weight->position.y = G3::StrToFloat (tokl.Tokens [5]);
                weight->position.z = G3::StrToFloat (tokl.Tokens [6]);
                // Add the weight
                weights.push_back (weight);

                // A triangle?
            } else if (tokl.Tokens [0] == "tri") {
                // Allocate memory for a new triangle
                tri = new TRIANGLE ();
                // FAIL?
                if (!tri) {
                    if (log)
                        log->Report ("ERROR! MD5_MODEL::MESH::Parse: Failed to allocate memory for a triangle..\n");
                    return false;
                }
                // NOTE:: We skip the index in tokl.Tokens [1]
                // Fetch the indices
                tri->indices [0] = (int) G3::StrToInteger (tokl.Tokens [2]);
                tri->indices [1] = (int) G3::StrToInteger (tokl.Tokens [3]);
                tri->indices [2] = (int) G3::StrToInteger (tokl.Tokens [4]);
                // Add the triangle
                triangles.push_back (tri);

                // A vertex?
            } else if (tokl.Tokens [0] == "vert") {
                // Allocate memory for a new vertex
                vert = new VERTEX ();
                // FAIL?
                if (!vert) {
                    if (log)
                        log->Report ("ERROR! MD5_MODEL::MESH::Parse: Failed to allocate memory for a vertex..\n");
                    return false;
                }
                // NOTE:: We skip the index in tokl.Tokens [1]
                // Fetch the corresponding texture coordinates
                vert->texCoord.x = G3::StrToFloat (tokl.Tokens [2]);
                vert->texCoord.y = G3::StrToFloat (tokl.Tokens [3]);
                // Fetch the start number of the vertex weight
                vert->weightStart = (int) G3::StrToInteger (tokl.Tokens [4]);
                // Fetch the total number of vertex weights
                vert->numWeights = (int) G3::StrToInteger (tokl.Tokens [5]);
                // Add the vertex
                preVerts.push_back (vert);

                // The path of the material?
            } else if (tokl.Tokens [0] == "shader") {
                // TODO:: Should consider whitespaces as a part of the filename
                materialName = tokl.Tokens [1];

                // The number of vertices?
            } else if (tokl.Tokens [0] == "numverts") {
                // Fetch the number of vertices
                numVertices = (int) G3::StrToInteger (tokl.Tokens [1]);
                // Reserve some space for vertices & texture coordinates
                preVerts.reserve (numVertices);
                vertices = new float [numVertices * 3];
                texcoords = new float [numVertices * 2];
                normals = new float [numVertices * 3];
                tangents = new float [numVertices * 3];

                // The number of triangles?
            } else if (tokl.Tokens [0] == "numtris") {
                numTriangles = (int) G3::StrToInteger (tokl.Tokens [1]);
                // Reserve some space for triangles
                triangles.reserve (numTriangles);
                indices = new int [numTriangles * 3];

                // The number of weights?
            } else if (tokl.Tokens [0] == "numweights") {
                numWeights = (int) G3::StrToInteger (tokl.Tokens [1]);
                // Reserve some space for the weights
                weights.reserve (numWeights);
            }
        }

        // End of scope?
        if (line.find ("}") != std::string::npos)
            break;
    }

    return true;
}

//==============================================================================
// Parses the whole *.md5_mesh file
//==============================================================================
bool G3::MD5_MODEL::ParseMesh (G3::STREAM *stream) {
    Assert (stream, "MD5_MODEL::ParseMesh: Stream argument is a NULL pointer..");

    std::string line;
    G3::TOKENLIST tokl;
    MESH *mesh = NULL;

    // Parse the whole MD5Mesh file
    while (!stream->EndOfFile ()) {
        // Read an uncommented line
        line = G3::CopyUntilDelim (stream->Readline ("\r\n"), "//");
        // Tokenize the line
        tokl = G3::Tokenize (line, " \t\"(){}");
        // There are at least 2 important tokens?
        if (tokl.Tokens.size () >= 2) {
            if (tokl.Tokens [0] == "numJoints")
                numJoints = (int) StrToInteger (tokl.Tokens [1]);
            else if (tokl.Tokens [0] == "numMeshes") {
                numMeshes = (int) StrToInteger (tokl.Tokens [1]);
                // Reserve for a certain amount of meshes
                meshes.reserve (numMeshes);
            }
            // Lets hope that everything is on its own line
        } else if (tokl.Tokens.size () == 1) {
            // Joints scope?
            if (tokl.Tokens [0] == "joints") {
                try {
                    // Delete any previous base skeleton that might exist..
                    delete baseSkeleton;
                } catch (...) {
                    if (log)
                        log->Report ("ERROR! MD5_MODEL::Parse: Failed to delete an old skeleton..\n");
                    return false;
                }

                try {
                    // Allocate memory for a skeleton if needed
                    baseSkeleton = new SKELETON ();

                    if (!baseSkeleton)
                        throw 1;
                } catch (...) {
                    if (log)
                        log->Report ("ERROR! MD5_MODEL::Parse: Failed to allocate memory for a skeleton..\n");
                    return false;
                }

                // Parse the joints
                baseSkeleton->log = log;
                if (!baseSkeleton->Parse (stream, numJoints))
                    return false;

                // Mesh scope?
            } else if (tokl.Tokens [0] == "mesh") {
                try {
                    // Allocate memory for a mesh
                    mesh = new MESH ();

                    if (!mesh)
                        throw 1;
                } catch (...) {
                    if (log)
                        log->Report ("ERROR! MD5_MODEL::Parse: Failed to allocate memory for a mesh..\n");
                    return false;
                }

                // Add a fresh mesh
                meshes.push_back (mesh);
                // Parse the mesh
                mesh->log = log;
                if (!mesh->Parse (stream))
                    return false;
            }
        }
    }

    return true;
}

//==============================================================================
// Calculates mesh vertices for rendering
//==============================================================================
void G3::MD5_MODEL::MESH::CalcVertices (SKELETON *skeleton) {
    Assert (skeleton, "MD5_MODEL::MESH::CalcVertices: Skeleton argument is a NULL pointer..");

    WEIGHT *weight = NULL;
    JOINT *joint = NULL;
    G3::VEC3F wv, v;
    G3::VEC3F wn, n;
    G3::VEC3F wt, t;

    for (int i=0; i<numVertices; i++) {
        v = G3::VEC3F (0, 0, 0);
        n = G3::VEC3F (0, 0, 0);
        t = G3::VEC3F (0, 0, 0);
        // Calculate final vertex to draw with weights
        for (int j=0; j<preVerts [i]->numWeights; j++) {
            weight = weights [preVerts [i]->weightStart + j];
            joint = skeleton->joints [weight->joint];

            // Calculate transformed vertex for this weight
            wv = G3::ApplyQuat (weight->position, joint->orientation);
            // The sum of all weight->bias should be 1.0
            v += (joint->position + wv) * weight->bias;

            // Calculate transformed normal for this weight
            wn = G3::ApplyQuat (weight->normal, joint->orientation);

            n += wn * weight->bias;

            // Calculate transformed tangent for this weight
            wt = G3::ApplyQuat (weight->tangent, joint->orientation);

            t += wt * weight->bias;
        }

        // Fill the arrays
        vertices [3 * i    ] = v.x;
        vertices [3 * i + 1] = v.y;
        vertices [3 * i + 2] = v.z;

        normals [3 * i    ] = n.x;
        normals [3 * i + 1] = n.y;
        normals [3 * i + 2] = n.z;

        tangents [3 * i    ] = t.x;
        tangents [3 * i + 1] = t.y;
        tangents [3 * i + 2] = t.z;
    }
}

// --------------------------------------------------------------------------
//
// der_ton said:
//
// * First you have to get the bind-pose model-space normals by calculating
//   them from the model geometry in bind-pose.
//
// * Then you calculate the weight's normal (which is in bone-space) by
//   invert-transforming the normal by the bone-space matrix.
//
// * So afterwards when animating, you'll transform the weight normal with
//   the animated bone-space matrix and add them all up and you'll get
//   back your animated vertex normal.
// --------------------------------------------------------------------------
//==============================================================================
// Calculates weight normals
//==============================================================================
void G3::MD5_MODEL::MESH::CalcWeightNormals (SKELETON *skeleton) {
    Assert (skeleton, "MD5_MODEL::MESH::CalcWeightNormals: Skeleton argument is a NULL pointer..");

    WEIGHT *weight = NULL;
    JOINT *joint = NULL;
    TRIANGLE *tri = NULL;
    G3::VEC3F wv, v;
    G3::VEC3F wn;
    G3::VEC3F a, b, c;
    G3::QUATERNIONF q;

    std::vector<G3::VEC3F> bindVerts (numVertices);
    std::vector<G3::VEC3F> bindNormals (numVertices);

    // Calculate bindpose vertices
    for (int i=0; i<numVertices; i++) {
        v = G3::VEC3F (0, 0, 0);
        // Calculate final vertex to draw with weights
        for (int j=0; j<preVerts [i]->numWeights; j++) {
            weight = weights [preVerts [i]->weightStart + j];
            joint = skeleton->joints [weight->joint];

            // Calculate transformed vertex for this weight
            wv = G3::ApplyQuat (weight->position, joint->orientation);
            // The sum of all weight->bias should be 1.0
            v += (joint->position + wv) * weight->bias;
        }

        // Fill the arrays
        bindVerts [i] = v;
    }

    // Calculate bindpose normals
    for (int i=0; i<numTriangles; i++) {
        tri = triangles [i];

        a = bindVerts [tri->indices [0]];
        b = bindVerts [tri->indices [1]];
        c = bindVerts [tri->indices [2]];

        wn = G3::GetNormal (a, b, c);

        for (int j=0; j<3; j++)
            bindNormals [tri->indices [j]] += wn;
    }

    // "Average" the surface normals, by normalizing them
    for (int i=0; i<numVertices; i++) {
        if (bindNormals [i].GetL1Norm () != 0.0f)
            bindNormals [i].Normalize ();
    }

    // At this stage we have all vertex normals computed
    // for the model geometry in bind-pos

    // Zero out all weight normals
    for (int i=0; i<numWeights; i++)
        weights [i]->normal = G3::VEC3F (0, 0, 0);

    // Compute weight normals by invert-transforming the normal
    // by the bone-space matrix
    for (int i=0; i<numVertices; i++) {
        // Calculate final normal
        for (int j=0; j<preVerts [i]->numWeights; j++) {
            weight = weights [preVerts [i]->weightStart + j];
            joint = skeleton->joints [weight->joint];

            // Calculate transformed normal for this weight
            q = joint->orientation;
            q.Invert ();
            q.Normalize ();

            weight->normal += G3::ApplyQuat (bindNormals [i], q);
        }
    }

    // Normalize all weight normals
    for (int i=0; i<numWeights; i++)
        weights [i]->normal.Normalize ();
}

//==============================================================================
// Calculates weight tangents
//==============================================================================
void G3::MD5_MODEL::MESH::CalcWeightTangents (SKELETON *skeleton) {
    Assert (skeleton, "MD5_MODEL::MESH::CalcVertices: Skeleton argument is a NULL pointer..");

    WEIGHT *weight = NULL;
    JOINT *joint = NULL;
    TRIANGLE *tri = NULL;
    G3::VEC3F wv, v;
    G3::VEC3F wn, n;
    G3::VEC3F wt, t;
    G3::VEC3F a, b;
    G3::VEC2F w0, w1;
    G3::VEC3F sDir, tDir;
    G3::QUATERNIONF q;

    std::vector<G3::VEC3F> bindVerts (numVertices);
    std::vector<G3::VEC3F> bindNormals (numVertices);
    std::vector<G3::VEC3F> bindTangents (numVertices);
    std::vector<G3::VEC3F> sTan (numVertices);
    std::vector<G3::VEC3F> tTan (numVertices);

    for (int i=0; i<numVertices; i++) {
        v = G3::VEC3F (0, 0, 0);
        n = G3::VEC3F (0, 0, 0);
        sTan [i] = G3::VEC3F (0, 0, 0);
        tTan [i] = G3::VEC3F (0, 0, 0);

        // Calculate final vertex to draw with weights
        for (int j=0; j<preVerts [i]->numWeights; j++) {
            weight = weights [preVerts [i]->weightStart + j];
            joint = skeleton->joints [weight->joint];

            // Calculate transformed vertex for this weight
            wv = G3::ApplyQuat (weight->position, joint->orientation);
            // The sum of all weight->bias should be 1.0
            v += (joint->position + wv) * weight->bias;

            // Calculate transformed normal for this weight
            wn = G3::ApplyQuat (weight->normal, joint->orientation);

            n += wn * weight->bias;
        }
        // Fill the arrays
        bindVerts [i] = v;
        bindNormals [i] = n;
    }

    // Calculate s-tangeants and t-tangeants at triangle level
    for (int i=0; i<numTriangles; i++) {
        tri = triangles [i];

        a = bindVerts [tri->indices [1]] - bindVerts [tri->indices [0]];
        b = bindVerts [tri->indices [2]] - bindVerts [tri->indices [0]];

        w0 = preVerts [tri->indices [1]]->texCoord - preVerts [tri->indices [0]]->texCoord;
        w1 = preVerts [tri->indices [2]]->texCoord - preVerts [tri->indices [1]]->texCoord;

        // Cross product
        float r = w0.x * w1.y - w0.y * w1.x;
        // Prevent division by zero
        if (r == 0.0f)
            r = 1.0f;

        float ovr = 1.0f / r;

        sDir.x = (w1.y * a.x - w0.y * b.x) * ovr;
        sDir.y = (w1.y * a.y - w0.y * b.y) * ovr;
        sDir.z = (w1.y * a.z - w0.y * b.z) * ovr;

        tDir.x = (w0.x * b.x - w1.x * a.x) * ovr;
        tDir.y = (w0.x * b.y - w1.x * a.y) * ovr;
        tDir.z = (w0.x * b.z - w1.x * a.z) * ovr;

        for (int j=0; j<3; j++) {
            sTan [tri->indices [j]] += sDir;
            tTan [tri->indices [j]] += tDir;
        }
    }

    for (int i=0; i<numVertices; i++) {
        n = bindNormals [i];
        t = sTan [i];

        // Gram-Schmidt orthogonalize
        bindTangents [i] = t - n * (n * t);
        if (bindTangents [i].GetL1Norm () != 0.0f)
            bindTangents [i].Normalize ();

        // Calculate handedness
        if (G3::CrossProduct (n, t) * tTan [i] < 0.0f)
            bindTangents [i] = -bindTangents [i];

        // Compute weight tangent
        for (int j=0; j<preVerts [i]->numWeights; j++) {
            weight = weights [preVerts [i]->weightStart + j];
            joint = skeleton->joints [weight->joint];

            // Compute inverse quaternion rotation
            q = joint->orientation;
            q.Invert ();
            q.Normalize ();

            wt = G3::ApplyQuat (bindTangents [i], q);

            weight->tangent += wt;
        }
    }

    // Normalize all weight tangents
    for (int i=0; i<numWeights; i++)
        weights [i]->tangent.Normalize ();
}

//==============================================================================
// Calculates bounding box of the mesh
//==============================================================================
void G3::MD5_MODEL::MESH::CalcBBox (SKELETON *skeleton) {
    Assert (skeleton, "MD5_MODEL::MESH::CalcVertices: Skeleton argument is a NULL pointer..");

    WEIGHT *weight = NULL;
    JOINT *joint = NULL;
    G3::VEC3F wv, v;

    for (int i=0; i<numVertices; i++) {
        v = G3::VEC3F (0, 0, 0);
        // Calculate final vertex to draw with weights
        for (int j=0; j<preVerts [i]->numWeights; j++) {
            weight = weights [preVerts [i]->weightStart + j];
            joint = skeleton->joints [weight->joint];

            // Calculate transformed vertex for this weight
            wv = G3::ApplyQuat (weight->position, joint->orientation);
            // The sum of all weight->bias should be 1.0
            v += (joint->position + wv) * weight->bias;
        }
        // Expand by this vertex
        bbox.Expand (v);
    }
}

//==============================================================================
// Sets up triangle indicies
//==============================================================================
void G3::MD5_MODEL::MESH::SetupIndices () {
    for (int i=0; i<numTriangles; i++) {
        indices [3 * i    ] = triangles [i]->indices [0];
        indices [3 * i + 1] = triangles [i]->indices [1];
        indices [3 * i + 2] = triangles [i]->indices [2];
    }
}

//==============================================================================
// Sets up textures and texture coordinates
//==============================================================================
void G3::MD5_MODEL::MESH::SetupTextures () {
    std::string path = G3::MainFilesys.GetPath (materialName.c_str ());

    material.Load (path.c_str ());

    for (int i=0; i<numVertices; i++) {
        texcoords [2 * i    ] = preVerts [i]->texCoord.x;
        texcoords [2 * i + 1] = preVerts [i]->texCoord.y;
    }
}

//==============================================================================
// Renders the mesh
//==============================================================================
void G3::MD5_MODEL::MESH::Render () {
    material.glApply ();

    glEnableClientState (GL_VERTEX_ARRAY);
    glEnableClientState (GL_NORMAL_ARRAY);
    glEnableClientState (GL_TEXTURE_COORD_ARRAY);

    // Upload mesh data to OpenGL
    glVertexPointer (3, GL_FLOAT, 0, vertices);
    glNormalPointer (GL_FLOAT, 0, normals);
    glTexCoordPointer (2, GL_FLOAT, 0, texcoords);

    // Draw the mesh
    glDrawElements (GL_TRIANGLES, numTriangles * 3,
                    GL_UNSIGNED_INT, indices);

    glDisableClientState (GL_TEXTURE_COORD_ARRAY);
    glDisableClientState (GL_NORMAL_ARRAY);
    glDisableClientState (GL_VERTEX_ARRAY);

    material.glUnApply ();
}

//==============================================================================
// Dumps the BaseFrame Joint into the log
//==============================================================================
void G3::MD5_MODEL::BASEFRAME_JOINT::Dump (std::string indent, G3::LOG *log) {
    if (log) {
        log->Report ("%sDumping BaseFrame Joint:\n"
                     "%s Position: %f %f %f\n"
                     "%s Orientation: %f %f %f %f\n",
                     indent.c_str (),
                     indent.c_str (), position.x, position.y, position.z,
                     indent.c_str (), orientation.x, orientation.y, orientation.z, orientation.w);
    }
}

//==============================================================================
// Dumps a joint into the log
//==============================================================================
void G3::MD5_MODEL::JOINT::Dump (std::string indent, G3::LOG *log) {
    if (log) {
        log->Report ("%sDumping joint \"%s\":\n"
                     "%s Parent: %d\n"
                     "%s Position: %f %f %f\n"
                     "%s Orientation: %f %f %f %f\n",
                     indent.c_str (), name.c_str (),
                     indent.c_str (), parent,
                     indent.c_str (), position.x, position.y, position.z,
                     indent.c_str (), orientation.x, orientation.y, orientation.z, orientation.w);
    }
}

//==============================================================================
// Dumps a vertex into the log
//==============================================================================
void G3::MD5_MODEL::VERTEX::Dump (std::string indent, G3::LOG *log) {
    if (log) {
        log->Report ("%sDumping Vertex:\n"
                     "%s TexCoord: %f %f\n"
                     "%s WeightStart: %d\n"
                     "%s NumWeights: %d\n",
                     indent.c_str (),
                     indent.c_str (), texCoord.x, texCoord.y,
                     indent.c_str (), weightStart,
                     indent.c_str (), numWeights);
    }
}

//==============================================================================
// Dumps a triangle into the log
//==============================================================================
void G3::MD5_MODEL::TRIANGLE::Dump (std::string indent, G3::LOG *log) {
    if (log) {
        log->Report ("%sDumping Triangle:\n"
                     "%s Indices: %d %d %d\n",
                     indent.c_str (),
                     indent.c_str (), indices [0], indices [1], indices [2]);
    }
}

//==============================================================================
// Dumps a weight into the log
//==============================================================================
void G3::MD5_MODEL::WEIGHT::Dump (std::string indent, G3::LOG *log) {
    if (log) {
        log->Report ("%sDumping Weight:\n"
                     "%s Joint: %d\n"
                     "%s Bias: %f\n"
                     "%s Position: %f %f %f\n"
                     "%s Normal: %f %f %f\n"
                     "%s Tangent: %f %f %f\n",
                     indent.c_str (),
                     indent.c_str (), joint,
                     indent.c_str (), bias,
                     indent.c_str (), position.x, position.y, position.z,
                     indent.c_str (), normal.x, normal.y, normal.z,
                     indent.c_str (), tangent.x, tangent.y, tangent.z);
    }
}

//==============================================================================
// Dumps a skeleton into the log
//==============================================================================
void G3::MD5_MODEL::SKELETON::Dump (std::string indent, G3::LOG *log) {
    if (log) {
        log->Report ("%sDumping Skeleton:\n"
                     "%s NumJoints: %d\n",
                     indent.c_str (),
                     indent.c_str (), joints.size ());

        for (int i=0; i<(int)joints.size (); i++)
            joints [i]->Dump (indent + " ", log);
    }
}

//==============================================================================
// Dumps a mesh into the log
//==============================================================================
void G3::MD5_MODEL::MESH::Dump (std::string indent, G3::LOG *log) {
    if (log) {
        log->Report ("%sDumping Mesh \"%s\":\n"
                     "%s MaterialName: \"%s\"\n"
                     "%s BBox: (%f %f %f) (%f %f %f)\n"
                     "%s NumVertices: %d\n",
                     indent.c_str (), name.c_str (),
                     indent.c_str (), materialName.c_str (),
                     indent.c_str (), bbox.Minimum.x, bbox.Minimum.y, bbox.Minimum.z, bbox.Maximum.x, bbox.Maximum.y, bbox.Maximum.z,
                     indent.c_str (), numVertices);

        for (int i=0; i<numVertices; i++) {
            preVerts [i]->Dump (indent + " ", log);
        }

        log->Report ("%s NumTriangles: %d\n", indent.c_str (), numTriangles);

        for (int i=0; i<numTriangles; i++) {
            triangles [i]->Dump (indent + " ", log);
        }

        log->Report ("%s NumWeights: %d\n", indent.c_str (), numWeights);

        for (int i=0; i<numWeights; i++) {
            weights [i]->Dump (indent + " ", log);
        }
    }
}
